#include "spi.h"

// -----------内部定义---------------
// 使用的是GPIOA的5/6/7引脚作为接口, 对应外设SPI1
#define MYSPI_Port GPIOA
#define MYSPI_Pin_SCK GPIO_Pin_5
#define MYSPI_Pin_MOSI GPIO_Pin_7
#define MYSPI_Pin_MISO GPIO_Pin_6
// NSS单独使用GPIOC的PC0引脚, 直接控制
#define MYSPI_Port_NSS GPIOC
#define MYSPI_Pin_NSS GPIO_Pin_0
// 对应的SPI模块
#define MYSPI SPI1

#define MYSPI_NSSLOW() GPIO_ResetBits(MYSPI_Port_NSS, MYSPI_Pin_NSS)
#define MYSPI_NSSHIGH() GPIO_SetBits(MYSPI_Port_NSS, MYSPI_Pin_NSS)

// -----------SPI相关操作-函数定义---------------
#define DUMMY 0xFF // 傀儡字节, 用于发送以触发接收动作
uint8_t sendByte, recvByte;
void mySPI_Init(void)
{
    // RCC
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

    // GPIO
    // SCK/MOSI
    GPIO_InitTypeDef gpio_iniStruct = {0};
    gpio_iniStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    gpio_iniStruct.GPIO_Pin = MYSPI_Pin_SCK | MYSPI_Pin_MOSI;
    gpio_iniStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(MYSPI_Port, &gpio_iniStruct);
    // MISO
    gpio_iniStruct.GPIO_Mode = GPIO_Mode_IPU;
    gpio_iniStruct.GPIO_Pin = MYSPI_Pin_MISO;
    GPIO_Init(MYSPI_Port, &gpio_iniStruct);
    // NSS
    gpio_iniStruct.GPIO_Mode = GPIO_Mode_Out_PP;
    gpio_iniStruct.GPIO_Pin = MYSPI_Pin_NSS;
    GPIO_Init(MYSPI_Port_NSS, &gpio_iniStruct);

    // --------------SPI外设---------------
    SPI_InitTypeDef spi_initStruct = {0};
    // 模式选择两根数据线的全双工模式
    spi_initStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    // 主机模式
    spi_initStruct.SPI_Mode = SPI_Mode_Master;
    // 数据大小8bit, 即每次传输一个字节
    spi_initStruct.SPI_DataSize = SPI_DataSize_8b;
    // 对应flash芯片支持mode0/3, 此处使用mode3
    spi_initStruct.SPI_CPOL = SPI_CPOL_High;
    spi_initStruct.SPI_CPHA = SPI_CPHA_2Edge;
    // NSS不使用本硬件外设, 而是使用GPIOC_0引脚手动控制
    spi_initStruct.SPI_NSS = SPI_NSS_Soft;
    // SPI速度不可超过18MHz
    spi_initStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    // 先发高bit, 因为flash芯片只支持MSB在前
    spi_initStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    // 是否发送端和接收端使用CRC循环冗余校验, 此处不使用
    spi_initStruct.SPI_CRCPolynomial = 0;
    SPI_Init(MYSPI, &spi_initStruct);

    // --------------使能---------------------
    SPI_Cmd(MYSPI, ENABLE);
}

uint32_t mySPI_SendRecv(uint8_t sendData, uint8_t *recvData)
{
    uint32_t spiCount = 0; // 等待延时的次数
    // 1. 判断当前SPI未在发送其他数据过程中
    // 根据TXE(发送缓冲区为空)判断是否可发送
    while (SET != SPI_I2S_GetFlagStatus(MYSPI, SPI_I2S_FLAG_TXE))
    {
        if (spiCount++ >= 0x1000)
            return 11; // SPI被占用
    }
    // 2. 发送一个数据sendData
    SPI_I2S_SendData(MYSPI, sendData);
    // 3. 等待发送完成
    spiCount = 0;
    while (SET != SPI_I2S_GetFlagStatus(MYSPI, SPI_I2S_FLAG_TXE))
    {
        if (spiCount++ >= 0x1000)
            return 12; // 发送没成功
    }
    // 4. 等待接收完成
    spiCount = 0;
    while (SET != SPI_I2S_GetFlagStatus(MYSPI, SPI_I2S_FLAG_RXNE))
    {
        if (spiCount++ >= 0x1000)
            return 13; // 发送没成功
    }
    // 5. 接收返回的值
    *recvData = SPI_I2S_ReceiveData(MYSPI);
    // 结束
    return 0;
}

// -----------FLASH相关操作-函数声明--------
// 写使能, 每一轮操作前需要先使能write
#define SPIFLASH_CMD_WRITEENABLE 0x06
// 读取状态寄存器1
#define SPIFLASH_CMD_READSTATUS 0x05
// 获取JEDEC ID命令, 获取企业ID和产品ID
// 本产品企业ID 0xEF,产品id是0x16和0x4017
#define SPIFLASH_CMD_JEDECID 0x9F
// 扇区擦除
#define SPIFLASH_CMD_SECTORERASE 0x20
// 按页写入
#define SPIFLASH_CMD_PAGEWRITE 0x02
// 连续读
#define SPIFLASH_CMD_SEQUENCEREAD 0x03

// 允许写入
void SPIFLASH_WriteEnable(void)
{
    // 拉低NSS, 表示要对存储芯片发送SPI数据
    MYSPI_NSSLOW();
    // SPI发送enable指令
    mySPI_SendRecv(SPIFLASH_CMD_WRITEENABLE, &recvByte);
    // 拉高NSS
    MYSPI_NSSHIGH();
}
// 读取状态值, 判断是否可继续操作
void SPIFLASH_WaitForWriteDone(void)
{
    // NSS拉低
    MYSPI_NSSLOW();
    // 发送命令码
    mySPI_SendRecv(SPIFLASH_CMD_READSTATUS, &recvByte);
    // 接收状态值并判断
    do
    {
        mySPI_SendRecv(DUMMY, &recvByte);
    } while (recvByte & 0x01);
    // NSS拉高
    MYSPI_NSSHIGH();
}
// 获取设备ID
uint32_t SPIFLASH_GetID(void)
{
    uint32_t jedecId = 0;
    // 拉低NSS
    MYSPI_NSSLOW();
    // 发送命令码
    mySPI_SendRecv(SPIFLASH_CMD_JEDECID, &recvByte);
    //  接收三个字节
    mySPI_SendRecv(DUMMY, &recvByte);
    jedecId |= (recvByte << 16);
    mySPI_SendRecv(DUMMY, &recvByte);
    jedecId |= (recvByte << 8);
    mySPI_SendRecv(DUMMY, &recvByte);
    jedecId |= recvByte;
    // NSS拉高
    MYSPI_NSSHIGH();
    // 返回结果
    return jedecId;
}
// 擦除某个地址对应的sector扇区
void SPIFLASH_SectorErase(uint32_t sctAddr)
{
    SPIFLASH_WriteEnable();
    // 先拉底NSS
    MYSPI_NSSLOW();
    // 发送命令码
    mySPI_SendRecv(SPIFLASH_CMD_SECTORERASE, &recvByte);
    // 发送地址高字节(A23~A16)
    mySPI_SendRecv((sctAddr >> 16) & 0xFF, &recvByte);
    // 发送地址中字节
    mySPI_SendRecv((sctAddr >> 8) & 0xFF, &recvByte);
    // 发送地址低字节
    mySPI_SendRecv(sctAddr & 0xFF, &recvByte);
    // 拉高NSS
    MYSPI_NSSHIGH();
    // 等待擦除完成
    SPIFLASH_WaitForWriteDone();
}
// 按页写, 参数1为页地址
// 此处考虑严谨, 应该进行页计算, 防止出现跨页写入情况
void SPIFLASH_PageWrite(uint32_t dataAddr, uint8_t *data, uint32_t dataLen)
{
    // 先使能写操作
    SPIFLASH_WriteEnable();
    // 拉低NSS
    MYSPI_NSSLOW();
    // 发送命令码
    mySPI_SendRecv(SPIFLASH_CMD_PAGEWRITE, &recvByte);
    // 发送地址值的三个字节
    // 发送地址高字节(A23~A16)
    mySPI_SendRecv((dataAddr >> 16) & 0xFF, &recvByte);
    // 发送地址中字节
    mySPI_SendRecv((dataAddr >> 8) & 0xFF, &recvByte);
    // 发送地址低字节
    mySPI_SendRecv(dataAddr & 0xFF, &recvByte);
    // 循环发送要写入的数据值
    while(dataLen--)
    {
        mySPI_SendRecv(*data, &recvByte);
        data++;
    }
    // NSS拉高
    MYSPI_NSSHIGH();
    // 等待写入完成
    SPIFLASH_WaitForWriteDone();
    
}
// 连续读, 从某个起始地址读取连续数据
void SPIFLASH_SequenceRead(uint32_t dataAddr, uint8_t *data, uint8_t dataLen)
{
    // 拉低NSS
    MYSPI_NSSLOW();
    // 发送命令码
    mySPI_SendRecv(SPIFLASH_CMD_SEQUENCEREAD, &recvByte);
    // 发送地址值的三个字节
    // 发送地址高字节(A23~A16)
    mySPI_SendRecv((dataAddr >> 16) & 0xFF, &recvByte);
    // 发送地址中字节
    mySPI_SendRecv((dataAddr >> 8) & 0xFF, &recvByte);
    // 发送地址低字节
    mySPI_SendRecv(dataAddr & 0xFF, &recvByte);
    // 循环接收数据
    while(dataLen--)
    {
        mySPI_SendRecv(DUMMY, data);
        data++;
    }
    // 拉高NSS
    MYSPI_NSSHIGH();
    // 无需等
}
